﻿using Microsoft.SharePoint.Client;
using Microsoft.SharePoint.Client.WebParts;
using OfficeDevPnP.Core;
using System;
using System.Xml;

namespace IteraProvisioning.Core
{
    /// <summary>
    /// Class that handles all page and web part related operations
    /// </summary>
    public static class PageExtensions
    {
        /// <summary>
        /// Add web part to a wiki style page
        /// </summary>
        /// <param name="web">Site to be processed - can be root web or sub site</param>
        /// <param name="folder">System name of the wiki page library - typically sitepages</param>
        /// <param name="webPart">Information about the web part to insert</param>
        /// <param name="page">Page to add the web part on</param>
        /// <exception cref="System.ArgumentException">Thrown when folder or page is a zero-length string or contains only white space</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when folder, webPart or page is null</exception>
        public static void AddWebPartToWikiPage(this Web web, string folder, WikiWebPartDefinition webPart, string page)
        {
            CheckString(folder, "folder");

            IsNull(webPart, "webPart");

            CheckString(page, "page");

            if (!web.IsObjectPropertyInstantiated("ServerRelativeUrl"))
            {
                web.Context.Load(web, w => w.ServerRelativeUrl);
                web.Context.ExecuteQuery();
            }

            var webServerRelativeUrl = UrlUtility.EnsureTrailingSlash(web.ServerRelativeUrl);
            var serverRelativeUrl = UrlUtility.Combine(folder, page);
            AddWebPartToWikiPage(web, webServerRelativeUrl + serverRelativeUrl, webPart);
        }

        /// <summary>
        /// Add web part to a wiki style page
        /// </summary>
        /// <param name="web">Site to be processed - can be root web or sub site</param>
        /// <param name="serverRelativePageUrl">Server relative url of the page to add the webpart to</param>
        /// <param name="webPart">Information about the web part to insert</param>
        /// <exception cref="System.ArgumentException">Thrown when serverRelativePageUrl is a zero-length string or contains only white space</exception>
        /// <exception cref="System.ArgumentNullException">Thrown when serverRelativePageUrl or webPart is null</exception>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Xml.XmlDocument.CreateTextNode(System.String)")]
        public static void AddWebPartToWikiPage(this Web web, string serverRelativePageUrl, WikiWebPartDefinition webPart)
        {
            CheckString(serverRelativePageUrl, "serverRelativePageUrl");

            IsNull(webPart, "webPart");

            File webPartPage = web.GetFileByServerRelativeUrl(serverRelativePageUrl);

            if (webPartPage == null)
            {
                return;
            }

            web.Context.Load(webPartPage);
            web.Context.Load(webPartPage.ListItemAllFields);
            web.Context.ExecuteQuery();

            XmlDocument xd = GetDocument(webPartPage);

            // - wpBoxDiv
            XmlElement wpBoxDiv = xd.CreateElement("div");

            InsertWpMarkup(xd, webPart, wpBoxDiv);

            CreateWebPartDivs(xd, wpBoxDiv, GetWPDef(webPartPage, webPart, web));

            UpdateWikiField(webPartPage.ListItemAllFields, xd);
            web.Context.ExecuteQuery();

        }

        private static XmlDocument GetDocument(File webPartPage)
        {
            string wikiField = (string)webPartPage.ListItemAllFields["WikiField"];

            XmlDocument xd = new XmlDocument();
            xd.PreserveWhitespace = true;
            xd.LoadXml(wikiField);
            return xd;
        }

        private static void InsertWpMarkup(XmlDocument xd, WikiWebPartDefinition webPart, XmlElement wpBoxDiv)
        {
            // Sometimes the wikifield content seems to be surrounded by an additional div? 
            XmlElement layoutsZoneInner = GetInnerLayout(xd, webPart);
            if (webPart.ToTop)
            {
                if (layoutsZoneInner.FirstChild != null)
                {
                    layoutsZoneInner.InsertBefore(wpBoxDiv, layoutsZoneInner.FirstChild);
                }
            }
            else
                layoutsZoneInner.AppendChild(wpBoxDiv);

            ConfigureWpMarkup(xd, layoutsZoneInner, wpBoxDiv, webPart);
        }

        private static void ConfigureWpMarkup(XmlDocument xd, XmlElement layoutsZoneInner, XmlElement wpBoxDiv, WikiWebPartDefinition webPart)
        {
            AddSpace(xd, layoutsZoneInner, wpBoxDiv, webPart);

            wpBoxDiv.Attributes.Append(CreateAttribute(xd, "class", "ms-rtestate-read ms-rte-wpbox"));
            wpBoxDiv.Attributes.Append(CreateAttribute(xd, "contentEditable", "false"));
        }

        private static XmlElement GetInnerLayout(XmlDocument xd, WikiWebPartDefinition webPart)
        {
            XmlElement layoutsTable = xd.SelectSingleNode("div/div/table") as XmlElement;
            if (layoutsTable == null)
            {
                layoutsTable = xd.SelectSingleNode("div/table") as XmlElement;
            }

            return layoutsTable.SelectSingleNode(string.Format("tbody/tr[{0}]/td[{1}]/div/div", webPart.Row, webPart.Column)) as XmlElement;
        }

        private static void CreateWebPartDivs(XmlDocument xd, XmlElement wpBoxDiv, WebPartDefinition wpdNew)
        {
            // - div1
            XmlAttribute[] attributes = new XmlAttribute[]
            {
                CreateAttribute(xd,"class", "ms-rtestate-read " + wpdNew.Id.ToString("D")),
                CreateAttribute(xd,"id", "div_" + wpdNew.Id.ToString("D")),
            };
            CreateDiv(wpBoxDiv, attributes, xd);


            // - div2
            attributes = new XmlAttribute[]
            {
                CreateAttribute(xd,"style", "display:none"),
                CreateAttribute(xd,"id", "vid_" + wpdNew.Id.ToString("D")),
            };
            CreateDiv(wpBoxDiv, attributes, xd);
        }

        private static void AddSpace(XmlDocument xd, XmlElement layoutsZoneInner, XmlElement wpBoxDiv, WikiWebPartDefinition webPart)
        {
            if (webPart.AddSpace)
            {
                XmlElement space = xd.CreateElement("p");
                XmlText text = xd.CreateTextNode(" ");
                space.AppendChild(text);
                layoutsZoneInner.InsertAfter(space, wpBoxDiv);
            }
        }

        private static void IsNull(object obj, string name)
        {
            if (obj == null)
            {
                throw new ArgumentNullException(name);
            }
        }

        private static void CheckString(string obj, string name)
        {
            if (string.IsNullOrEmpty(obj))
            {
                throw (obj == null)
                  ? new ArgumentNullException(name)
                  : new ArgumentException(CoreResources.Exception_Message_EmptyString_Arg, name);
            }
        }

        private static WebPartDefinition GetWPDef(File webPartPage, WikiWebPartDefinition webPart, Web web)
        {
            LimitedWebPartManager limitedWebPartManager = webPartPage.GetLimitedWebPartManager(PersonalizationScope.Shared);
            WebPartDefinition oWebPartDefinition = limitedWebPartManager.ImportWebPart(webPart.WebPartXml);
            WebPartDefinition wpdNew = limitedWebPartManager.AddWebPart(oWebPartDefinition.WebPart, "wpz", 0);
            web.Context.Load(wpdNew);
            web.Context.ExecuteQuery();
            return wpdNew;
        }

        private static void UpdateWikiField(ListItem listItem, XmlDocument xd)
        {
            listItem["WikiField"] = xd.OuterXml;
            listItem.Update();
        }

        private static void CreateDiv(XmlElement wpBoxDiv, XmlAttribute[] attributes, XmlDocument xd)
        {
            XmlElement div = xd.CreateElement("div");
            wpBoxDiv.AppendChild(div);
            div.IsEmpty = false;
            foreach (var xmlAttribute in attributes)
            {
                div.Attributes.Append(xmlAttribute);
            }
        }

        private static XmlAttribute CreateAttribute(XmlDocument xd, string atrName, string atrValue)
        {
            XmlAttribute attribute = xd.CreateAttribute(atrName);
            attribute.Value = atrValue;
            return attribute;
        }
    }
}
